import logging
import os
import re
import urlparse
import urllib

from google.appengine.api import users
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template

import store

from openid.consumer import consumer as open_consumer
from openid.consumer import discover
from openid.extensions import pape, ax, sreg
from glp.django import forms
from glp.users import session
from glp.users import login
import glp.users

#!/usr/bin/python
#
# Copyright 2008 Google Inc.
#
# Much of this code was inspired by/taken from this sample project:
# http://code.google.com/p/google-app-engine-samples/
# which is licensed under the Apache License, Version 2.0 (the "License").
# You may obtain a copy of the license here:
#          http://www.apache.org/licenses/LICENSE-2.0
#
# Changes to the source were made by author(s) named below.

"""Handlers specific to management of OpenID requests."""

__author__ = 'alyssad@google.com (Alyssa Daw)'

_DEBUG = True
_EMAIL_REQUEST = 'http://axschema.org/contact/email'


class Handler(webapp.RequestHandler):
  """A base handler class with a couple OpenID-specific utilities."""
  consumer = None
  session = None
  session_args = None

  def __init__(self):
    self.session_args = {}

  def GetConsumer(self):
    """Returns a Consumer instance."""
    if not self.consumer:
      if not self.LoadSession():
        return
      self.consumer = open_consumer.Consumer(
          self.session_args, store.DatastoreStore())

    return self.consumer

  def ArgsToDict(self):
    """Converts the URL and POST parameters to a singly-valued dictionary.

    Returns:
      dict with the URL and POST body parameters
    """
    req = self.request
    return dict([(arg, req.get(arg)) for arg in req.arguments()])

  def LoadSession(self):
    """Loads the current session."""
    if not self.session:
      id = self.request.get('session_id')
      if id:
        try:
          self.session = db.get(db.Key.from_path('Session', int(id)))
          assert self.session
        except (AssertionError, db.Error), e:
          self.ReportError('Invalid session id: %d' % id)
          return None

        fields = self.session.dynamic_properties()
        self.session_args = dict((f, getattr(self.session, f)) for f in fields)

      else:
        self.session_args = {}
        self.session = session.Session()
        self.session.claimed_id = self.request.get('domain')

      return self.session

  def StoreSession(self):
    """Stores the current session."""
    assert self.session
    for field, value in self.session_args.items():
      setattr(self.session, field, value)
    self.session.put()

  def ReportError(self, message, exception=None):
    """Show an error message.

    Args:
      message: string
      A detailed error message.
    """
    if exception:
      logging.exception('Error: %s' % message)
      self.response.out.write(exception)
    self.response.out.write(message)


class OpenIdSignin(Handler):
  """Signs in a new user."""

  def post(self):
    """Handles login requests."""
    federated_identity = self.request.get('domain')
    redirect_url = urllib.unquote(self.request.get('return'))

    auth_request = self._GetAuthRequest(federated_identity)

    if not auth_request:
      return

    self._SetupSession(auth_request)

    self._AddExtensions(auth_request)

    url = self._GetRedirectUrl(auth_request, redirect_url)

    self.redirect(url)

  def get(self):
    self.post()

  def _GetAuthRequest(self, openid_url):
    try:
      consumer = self.GetConsumer()
      if consumer:
        return consumer.begin(openid_url)
    except discover.DiscoveryFailure, e:
      self.ReportError('Error during OpenID provider discovery.', e)
    except discover.XRDSError, e:
      self.ReportError('Error parsing XRDS from provider.', e)
    return None

  def _SetupSession(self, auth_request):
    self.session.claimed_id = auth_request.endpoint.claimed_id
    self.session.server_url = auth_request.endpoint.server_url
    self.session.store_and_display = True
    self.StoreSession()

  def _AddExtensions(self, auth_request):

    self._AddPapeExtension(auth_request)
    self._AddAxExtension(auth_request)

  def _AddPapeExtension(self, auth_request):
    pape_request = pape.Request(max_auth_age=0)
    auth_request.addExtension(pape_request)

  def _AddAxExtension(self, auth_request):
    ax_request = ax.FetchRequest()
    ax_request.add(ax.AttrInfo(
        _EMAIL_REQUEST, required=True, alias='email'))
    auth_request.addExtension(ax_request)

  def _GetRedirectUrl(self, auth_request, redirect_url):
    request_parts = list(urlparse.urlparse(self.request.uri))
    redirect_parts = list(urlparse.urlparse(redirect_url))
    request_parts[2] = redirect_parts[2]
    request_parts[4] = 'session_id=%d&%s' % (
        self.session.key().id(), redirect_parts[4])
    request_parts[5] = ''

    return_to = urlparse.urlunparse(request_parts)

    realm = urlparse.urlunparse(request_parts[0:2] + [''] * 4)

    redirect_url = auth_request.redirectURL(realm, return_to)
    return redirect_url


class FinishHandler(Handler):
  """Handle a redirect from the provider."""

  def get(self, user_type):
    args = self.ArgsToDict()
    consumer = self.GetConsumer()
    if not consumer:
      return

    response = consumer.complete(args, self.request.uri)
    assert response.status in login.Login.status.choices

    if response.status == 'failure':
      logging.error(str(response))
      self.ReportError(str(response))
      return
    elif response.status == 'cancel':
      return

    # Else the response was successful
    # Get the user's data (we only care about the email)
    ax_data = ax.FetchResponse.fromSuccessResponse(response)
    email = ax_data.data.get(_EMAIL_REQUEST)[0]

    # Update the session and save the login.
    self._UpdateSession(response)
    self._SaveLogin(response)
    if user_type == 'new':
      success = self._CheckFormAndSaveUser(email, user_type)
    else:
      success = self._SignInExistingUser(email)

    if success:
      self.redirect('/signin/%s' % user_type)

  def _SaveLogin(self, response):
    user_login = login.Login(status=response.status,
                             claimed_id=self.session.claimed_id,
                             server_url=self.session.server_url,
                             session=self.session.key())
    user_login.put()

  def _UpdateSession(self, response):
    self.session.claimed_id = response.endpoint.claimed_id
    self.session.server_url = response.endpoint.server_url

  def _CheckFormAndSaveUser(self, email, user_type):
    form = forms.RegistrationForm(data=self.request)
    if form.is_valid():
       return self._CheckRegisteredAndSave(email, form, user_type)
    else:
      # The form was invalid, so show the form again.
      context = { 'registration_form' : form , 'post_url' : self.request.uri }
      self.response.out.write(template.render(
          'templates/register.html', context, debug=_DEBUG))
      return False

  def _CheckRegisteredAndSave(self, email, form, user_type):
    glp_user = glp.users.GetUser(email)
    if not glp_user:
      # Save the user (normal case).
      registration = form.save(commit=False)
      registration.email = email
      registration.confirmed = True
      registration.put()
      glp.users.LoginUser(registration)
      return registration
    else:
      # The current user has already registered so sign them in and show
      # already_registered.
      glp.users.LoginUser(glp_user)
      context = { 'user_email' : email,
                  'logout_url' : glp.users.create_logout_url('/users/login.html') }
      self.response.out.write(template.render(
          'templates/registered_already.html', context, debug=_DEBUG))
      return False

  def _SignInExistingUser(self, email):
    user = glp.users.GetUser(email)
    if user:
      glp.users.LoginUser(user)
      return True
    else:
      context = { 'email' : email }
      self.response.out.write(template.render(
          'templates/not_registered.html', context, debug=_DEBUG))
      return False


def main():
  logging.getLogger().setLevel(logging.DEBUG)
  application = webapp.WSGIApplication([
      ('/begin/.*', OpenIdSignin),
      ('/finish/(.*)/.*', FinishHandler),
      ], debug=_DEBUG)
  run_wsgi_app(application)

if __name__ == "__main__":
  main()
